﻿using Google.Protobuf.Collections;
using GrapeCity.Forguncy.Commands;
using GrapeCity.Forguncy.Plugin;
using Qdrant.Client.Grpc;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace QdrantConnector
{
    [Icon("pack://application:,,,/QdrantConnector;component/Resources/Icon.png")]
    [Designer("QdrantConnector.Designer.QdrantConnectorServerCommandDesigner, QdrantConnector")]
    [Category("Qdrant向量数据库")]
    [OrderWeight(100)]
    internal class SearchInCollection : BaseQdrantOp
    {
        [FormulaProperty]
        [DisplayName("向量（queryEmbedding）")]
        [OrderWeight(100)]
        [Description("由浮点数组成的数组，可通过AI大模型生成，形如[0.112,0.9871,...]")]
        public object EmbeddingFormula { get; set; }

        [FormulaProperty]
        [DisplayName("最大返回数量（topK）")]
        [OrderWeight(101)]
        public object TopKFormula { get; set; }


        [FormulaProperty]
        [DisplayName("需跳过的数量（offset）")]
        [OrderWeight(102)]
        public object OffsetFormula { get; set; }


        [DisplayName("是否需要返回负载（payload）")]
        [Description("默认不返回任何负载，仅返回ID。")]
        [BoolProperty]
        [OrderWeight(110)]
        public bool ShouldReturnPayload { get; set; }

        [OrderWeight(120)]
        [DisplayName("点击设置查询条件（filter）")]
        [Description("查询条件为AND关系，即所有条件全部满足才能纳入距离计算范围，才有机会返回。")]
        [ListProperty]
        public List<QueryConditionObject> QueryConditions { get; set; }

        [DisplayName("将匹配结果保存到变量")]
        [OrderWeight(999)]
        [Description("匹配结果为数组，对象的id属性为向量记录的文本ID，distance为查询条件与该记录的向量距离，数值越小说明越接近，payload为负载")]
        [ResultToProperty]
        public string ResultInParamater { get; set; } = "MatchedArray";

        public override async Task<ExecuteResult> ExecuteAsync(IServerCommandExecuteContext dataContext)
        {
            var embedding = (await dataContext.EvaluateFormulaAsync(this.EmbeddingFormula))?.ToString() ?? "[]";

            var topK = (await dataContext.EvaluateFormulaAsync(this.TopKFormula))?.ToString() ?? "5";
            var offset = (await dataContext.EvaluateFormulaAsync(this.OffsetFormula))?.ToString() ?? "0";

            var client = await getQrantClient(dataContext);

            var collectionName = await getCollectionName(dataContext);
            var filters= await QdrantFilterBuilder.BuildFilterAsync(dataContext, QueryConditions);
            var selector = Converters.ToPayloadSelector(ShouldReturnPayload);
            var embeddingVector = Converters.ToVector(embedding);

            dataContext.Log.AppendLine($"开始在Qdrant集合【{collectionName}】中搜索...");
            var tick = DateTime.Now;

            var points = await client.SearchAsync(
                collectionName,
                embeddingVector,
                filter: filters,
                payloadSelector: selector,
                limit: (ulong)int.Parse(topK),
                offset: (ulong)int.Parse(offset)
                );

            dataContext.Log.AppendLine($"在集合 【{collectionName}】中找到{points.Count}个记录，耗时：{(DateTime.Now - tick).TotalMilliseconds}ms.");

            List<SearchResult> result = new List<SearchResult>();

            foreach (var point in points)
            {
                result.Add(new SearchResult(
                    id: (int)point.Id.Num,
                    score: point.Score,
                    payload: point.Payload.ToDictionary(kvp => kvp.Key, kvp => Converters.ToObject(kvp.Value))
                ));

            }

            if (!string.IsNullOrEmpty(ResultInParamater))
            {
                dataContext.Parameters[ResultInParamater] = result;
            }

            return new ExecuteResult();
        }



        public override string ToString()
        {
            return "Qdrant搜索";
        }

        public class SearchResult
        {
            public int id { get; set; }
            public float score { get; set; }

            public Dictionary<string, object> payload { get; set; }

            public SearchResult(int id, float score, Dictionary<string, object> payload)
            {
                this.id = id;
                this.score = score;
                this.payload = payload;
            }
        }


    }
}
